﻿using sdkrtv2;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VoiceAI;
using Windows.Data.Json;
using Windows.UI.Xaml.Media.Imaging;
using XiaoweiV2;
using XiaoweiVoiceAI.Diagnostics;
using XiaoweiVoiceAI.Model;

namespace XiaoweiVoiceAI
{
    public class XiaoweiVoiceAI : IVoiceAI
    {
        #region events
        public event LoginEventHandler Login;
        public event LogoutEventHandler Logout;
        public event InitializeEventHandler Initialized;

        public event AvailablityEventHandler AvailabilityChanged;
        public event ResponseErrorEventHandler ResponseError;

        public event QrCodeEventHandler QrCodeChanged;

        public event AlarmEventHandler AlarmChanged;

        public event KeywordEventHandler KeywordConfirmed;
        public event KeywordEventHandler KeywordRejected;

        public event Action<string> SpeechRecognizing;
        public event Action<string> SpeechRecognized;
        public event ConversationEventHandler ProcessEnded;

        public event ConversationCountHandler CountChanged;
        public event Action<IResponseData> ResponseReceived;
        #endregion

        #region Public members
        public bool IsMuted { get; set; } = false;
        public bool IsOnline { get; set; }

        public bool KeywordVerificationEnabled { get; set; } = false;
        public bool SaveSpeechEnabled { get; set; } = false;
        public bool SaveLogEnabled { get; set; } = false;
        public int LogLevel { get; set; } = 3;
        public bool HasError { get; private set; } = false;
        public bool IsResourcesFromGetMore { get; set; }

        public bool HasPlayable { get; private set; }

        private Context Context = new Context();

        private Events responseEvent = Events.Idle;

        private bool responseArrived = false;

        private static XiaoweiVoiceAI mInstance = null;
        public static XiaoweiVoiceAI Instance
        {
            get
            {
                if (mInstance == null)
                {
                    mInstance = new XiaoweiVoiceAI();
                }
                return mInstance;
            }

        }
        //private FailsafeTimer failsafeTimer = new FailsafeTimer();//it is used to protect the context
        private bool hasContext = false;
        public bool HasContext
        {
            get { return hasContext; }
            private set
            {
                //Debug.WriteLine($"XiaoweiSDK, failsafeTimer.Stop, value:{value}");
                //failsafeTimer.Stop();
                if (hasContext != value)
                {
                    hasContext = value;
                    if (hasContext)
                    {
                        //Debug.WriteLine($"XiaoweiSDK, failsafeTimer.Start");
                        //failsafeTimer.Start(15000);
                    }
                }
            }
        }
        public ulong Din { get; private set; }

        public string QRCode { get; set; }

        public BitmapImage QRCodeImage { get; set; }

        private Int64 count = 0;
        public Int64 Count
        {
            get { return count; }
            set
            {
                if (value != count)
                {
                    count = value;
                    CountChanged?.Invoke(value);
                }
            }
        }
        #endregion
        public XiaoweiVoiceAI()
        {
            //failsafeTimer.FailsafeTimeout += () =>
            //{
            //    Debug.WriteLine($"XiaoweiSDK, FailsafeTimeout");
            //    HasContext = false;
            //};
        }




        ~XiaoweiVoiceAI()
        {
            DisposeWorkThread();
        }
        #region Private members
        private bool IsInitialized { get; set; }
        private string PlayId { get; set; }// only for test to get more
        private Skill Skill { get; set; }// only for test to get more

        private bool isKeywordRecognizing = false;
        private bool isSpeechRecognizing = false;
        //
        private bool isSpeechRecognizingFinished = false;

        private ClassDeviceSDK sdk;

        public string UserId { get; set; }
        public string UserName { get; set; }
        public bool IsLogin { get; private set; } = false;

        #endregion

        #region Init
        public async void InitAsync(string sn, string signature, string appId, int pid, int keyVersion)
        {
            CreateWorkThread();

            await Task.Run(() =>
            {
                IsInitialized = false;
                ClassDeviceSDK.callBackOnLog -= ClassDeviceSDK_callBackOnLog;
                ClassDeviceSDK.callBackOnNetDelay -= ClassDeviceSDK_callBackOnNetDelay;
                ClassDeviceSDK.callBackOnLoginComplete -= ClassDeviceSDK_callBackOnLoginComplete;
                ClassDeviceSDK.callBackOnLogOutComplete -= ClassDeviceSDK_callBackOnLogOutComplete;
                ClassDeviceSDK.callBackOnNetStatusChange -= ClassDeviceSDK_callBackOnNetStatusChange;
                ClassDeviceSDK.callBackOnQrCodeRefresh -= ClassDeviceSDK_callBackOnQrCodeRefresh;
                ClassDeviceSDK.callBackOnBinderListChange -= OnBinderListChanged;
                ClassDeviceSDK.callBackOnBinderVerify -= ClassDeviceSDK_callBackOnBinderVerify;
                ClassDeviceSDK.callBackOnAvatarUpdate -= ClassDeviceSDK_callBackOnAvatarUpdate;
                ClassDeviceSDK.callBackOnNicknameUpdate -= ClassDeviceSDK_callBackOnNicknameUpdate;
                ClassDeviceSDK.callBackOnRequest -= ClassDeviceSDK_callBackOnRequest;
                ClassDeviceSDK.callBackOnRequestCMD -= ClassDeviceSDK_callBackOnRequestCMD;

                sdk = new ClassDeviceSDK();

                ClassDeviceSDK.callBackOnLog += ClassDeviceSDK_callBackOnLog;
                ClassDeviceSDK.callBackOnNetDelay += ClassDeviceSDK_callBackOnNetDelay;
                ClassDeviceSDK.callBackOnLoginComplete += ClassDeviceSDK_callBackOnLoginComplete;
                ClassDeviceSDK.callBackOnLogOutComplete += ClassDeviceSDK_callBackOnLogOutComplete;
                ClassDeviceSDK.callBackOnNetStatusChange += ClassDeviceSDK_callBackOnNetStatusChange;
                ClassDeviceSDK.callBackOnQrCodeRefresh += ClassDeviceSDK_callBackOnQrCodeRefresh;
                ClassDeviceSDK.callBackOnBinderListChange += OnBinderListChanged;
                ClassDeviceSDK.callBackOnBinderVerify += ClassDeviceSDK_callBackOnBinderVerify;
                ClassDeviceSDK.callBackOnAvatarUpdate += ClassDeviceSDK_callBackOnAvatarUpdate;
                ClassDeviceSDK.callBackOnNicknameUpdate += ClassDeviceSDK_callBackOnNicknameUpdate;
                ClassDeviceSDK.callBackOnRequest += ClassDeviceSDK_callBackOnRequest;
                ClassDeviceSDK.callBackOnRequestCMD += ClassDeviceSDK_callBackOnRequestCMD;

                var ret = sdk._device_init(sn, signature, appId, pid, keyVersion, 1, 0);

                Debug.WriteLine($"_device_init, ret:{ret}, sn:{sn}, signature:{signature}, appId:{appId}, pid:{pid}, keyVersion:{keyVersion}");
            }).ConfigureAwait(false);
        }
        #endregion

        #region callbacks
        private void ClassDeviceSDK_callBackOnNicknameUpdate(int error_code, string nickname)
        {
            Debug.WriteLine("ClassDeviceSDK_callBackOnNicknameUpdate");
        }

        private void ClassDeviceSDK_callBackOnAvatarUpdate(int error_code, string avatar_url)
        {
            Debug.WriteLine("ClassDeviceSDK_callBackOnAvatarUpdate");
        }

        private bool ClassDeviceSDK_callBackOnNetDelay(string voice_id, ulong time)
        {
            //Log.WriteLine($"NetDelay, voice_id:{voice_id}, time:{time/2}");
            return true;
        }

        private void ClassDeviceSDK_callBackOnLog(int level, string tag, string filename, int line, string funcname, string data)
        {
            if (SaveLogEnabled && level >= LogLevel)
            {
                var log = $"level:{level}, tag:{tag}, filename:{filename}, line:{line}, funcname:{funcname}, data:{data}";
                Debug.WriteSdkLog(log);
            }
        }

        private void ClassDeviceSDK_callBackOnBinderVerify(TX_BINDER_INFO_CS binderInfo)
        {
            Debug.WriteLine("ClassDeviceSDK_callBackOnBinderVerify");
        }

        private void ClassDeviceSDK_callBackOnLogOutComplete(int error_code)
        {
            Debug.WriteLine($"ClassDeviceSDK_callBackOnLogOutComplete, {error_code}");
        }

        private void ClassDeviceSDK_callBackOnRequestCMD(string voice_id, int error_code, string json_data)
        {
            Debug.WriteLine($"OnRequestCMD, voice_id:{voice_id}, err:{error_code}, {json_data}");
            if (error_code == 0)
            {
                AlarmChanged?.Invoke(json_data);
            }
        }

        private void OnBinderListChanged(int error_code, IList<TX_BINDER_INFO_CS> binderList)
        {
            Debug.WriteLine($"OnBinderListChanged, binderList.Count:{binderList.Count}, error_code:{error_code}");
            if (error_code != 0) return;
            // filter out all binder changes during initializing.
            if (!IsInitialized) return;

            GetBinderInfo(binderList);

            try
            {
                if (binderList.Count > 0)
                {
                    if (!IsLogin)
                    {
                        IsLogin = true;
                        Login?.Invoke(UserId, UserName);
                    }
                }
                else
                {
                    if (IsLogin)
                    {
                        Logout?.Invoke();
                        IsLogin = false;
                    }
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine($"There is something exception in binder change callback! e:{e}");
            }
        }

        private void ClassDeviceSDK_callBackOnQrCodeRefresh(int error_code, string qr_code_path, uint expire_time, uint expire_in)
        {
            Debug.WriteLine($"ClassDeviceSDK_callBackOnQrCodeRefresh, err:{error_code}, code:{qr_code_path}, expire_time:{expire_time}, expire_in:{expire_in}");
            if (error_code == 0)
            {
                //TODO:need to implement expire time update
                QRCode = qr_code_path;
                QrCodeChanged?.Invoke(qr_code_path);
            }
        }

        private void ClassDeviceSDK_callBackOnNetStatusChange(int status)
        {

            Debug.WriteLine($"ClassDeviceSDK_callBackOnNetStatusChange:{status}");

            Task.Run(() =>
            {
                try
                {
                    Debug.WriteLine($"SDK Online state:{status}");
                    UpdateOnlineStatus(status == 1 ? true : false);
                    Debug.WriteLine($"SDK Online state:{status} end");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"ClassDeviceSDK_callBackOnNetStatusChange ex:{ex}");
                }
            });

        }

        private void ClassDeviceSDK_callBackOnLoginComplete(int error_code)
        {
            try
            {
                OnLoginComplete(error_code);
            }
            catch (Exception e)
            {
                Debug.WriteLine($"There is something exception in login callback. e:{e}");
            }
        }

        private bool ClassDeviceSDK_callBackOnRequest(string voice_id, TXCA_EVENT_CS @event, TXCA_PARAM_RESPONSE_CS state_info, string extend_info, uint extend_info_len)
        {
            try
            {
                ResponseData data = new ResponseData()
                {
                    VoiceId = voice_id,
                    Evt = (Events)@event,
                    Param = TypeHelper.ResponseParamConvert(state_info),
                    ExtendInfo = extend_info,
                    ExtendInfoLen = (int)extend_info_len
                };

                //fix 6684 can't recognize the vocie
                if (data.Param.ErrorCode == 33 && isSpeechRecognizing)
                {
                    Debug.WriteLine("Voice package can't be sent");
                    ResponseError?.Invoke(10033);
                    return true;
                }

                ProcessResponseDataInCallbackThread(data);
            }
            catch (Exception e)
            {
                Debug.WriteLine($"There is something exception in callback thread. e:{e}");
            }

            return true;
        }
        #endregion

        private Thread workThread = null;
        private volatile bool IsWorkThreadCreated = false;
        private Semaphore workerSemphore = null;
        private readonly ConcurrentQueue<ResponseData> responseQueue = new ConcurrentQueue<ResponseData>();

        public void CreateWorkThread()
        {
            if (!IsWorkThreadCreated)
            {
                workerSemphore = new Semaphore(0, 800000);
                IsWorkThreadCreated = true;
                workThread = new Thread(() =>
                {
                    Debug.WriteLine("XiaoweiSDK.CreateWorkerThread created!");
                    while (IsWorkThreadCreated)
                    {
                        //Debug.WriteLine("lock wait:XiaoweiSDK::CreateWorkerThread");
                        workerSemphore.WaitOne();
                        //Debug.WriteLine("lock enter:XiaoweiSDK::CreateWorkerThread");
                        ResponseDataWorker();
                    }
                    Debug.WriteLine("XiaoweiSDK.WorkerThread exited!");
                });
                workThread.IsBackground = true;
                workThread.Start();
            }
        }

        public void DisposeWorkThread()
        {
            Debug.WriteLine("XiaoweiSDK.DisposeWorkThread");
            IsWorkThreadCreated = false;
            workerSemphore?.Release();
            workThread?.Join();
            workThread = null;
            if (workerSemphore != null)
            {
                workerSemphore.Dispose();
                workerSemphore = null;
            }
        }

        private void ResponseDataWorker()
        {
            if (responseQueue.TryDequeue(out ResponseData data))
            {
                try
                {
                    ProcessSpeechResponseInWorkThread(data);
                }
                catch (Exception e)
                {
                    Debug.WriteLine($"There is something exception in processing speech response. e:{e}");
                }
            }
        }

        private void ProcessResponseDataInCallbackThread(ResponseData data)
        {
            responseEvent = data.Evt;

            var type = Session.GetTypeByVoiceId(data.VoiceId);
            if (type == null)
            {
                type = SessionType.Unknown;
            }

            if (type == SessionType.KeywordSpotting)
            {
                //process keyword verification response
                ResponseHelper.DebugKeywordResponse(Din, data.VoiceId, data.Param.ErrorCode, data.Evt, data.Param.WakeupFlag);

                ProcessKeywordResponseInCallbackThread(data);
            }
            else
            {
                //process speech reponses
                ResponseHelper.DebugResponse(Din, data, type.Value);

                ProcessSpeechResponseInCallbackThread(data);

                //if it's muted, ignore the response
                if (IsMuted && !IsResourcesFromGetMore) return;

                NotifyWorkThreadToProcessSpeechResponseData(data);
            }
        }
        private void ProcessKeywordResponseInCallbackThread(ResponseData data)
        {
            switch (data.Evt)
            {
                case Events.Response:
                    if (isKeywordRecognizing)
                    {
                        isKeywordRecognizing = false;
                        //IsListening = false;
                        Debug.WriteLine("!!!KWS DONE!");

                        var err = data.Param.ErrorCode;
                        var wakeupFlag = data.Param.WakeupFlag;

                        var isKeyword = (err == 0) &&
                            (wakeupFlag == WakeupFlag.Success ||
                             wakeupFlag == WakeupFlag.SuccessRsp ||
                             wakeupFlag == WakeupFlag.SuccessContinue);

                        if (isKeyword)
                        {
                            KeywordConfirmed?.Invoke();
                        }
                        else
                        {
                            KeywordRejected?.Invoke();
                        }
                    }
                    DebugWhenFrameReady("ProcessKeywordResponseInCallbackThread");
                    break;
                case Events.Idle:
                    if (isKeywordRecognizing)
                    {
                        isKeywordRecognizing = false;
                        //IsListening = false;
                        Debug.WriteLine($"!!!KWS DONE 2, evt:{data.Evt}, voiceId:{data.VoiceId}");
                        KeywordRejected?.Invoke();
                    }
                    break;
            }
        }

        private void ProcessSpeechResponseInCallbackThread(ResponseData data)
        {
            switch (data.Evt)
            {
                case Events.Recognize:
                    if (IsRecognizeFinished(data.ExtendInfo, out string txt))
                    {
                        isSpeechRecognizing = false;
                        //IsListening = false;
                    }
                    break;
                case Events.Silent:
                case Events.Idle:
                    //IsListening = false;
                    isSpeechRecognizing = false;
                    break;

                default: break;
            }
        }

        private void NotifyWorkThreadToProcessSpeechResponseData(ResponseData data)
        {
            Task.Run(()=> {
                lock (defaultVoiceIdTable)
                {
                    if (defaultVoiceIdTable.ContainsKey(data.VoiceId))
                    {
                        defaultVoiceIdTable[data.VoiceId].ResponseParam = data.Param;
                        defaultVoiceIdTable[data.VoiceId].waiter.Set();
                        return;
                    }
                }
                responseQueue.Enqueue(data);
                workerSemphore?.Release();
            });

        }
        public void GetMoreResources(Skill skill, string playId, bool isUp)
        {
            Debug.WriteLine($"-- GetMoreResources, skill:{skill.Name} playId:{playId}");

            SendMoreResourceCmd(skill.Id, skill.Name, playId, isUp);
        }
        private static readonly Dictionary<string, ISendMore> MoreResourceModels = new Dictionary<string, ISendMore>()
        {
            { Skills.FmStory,FmStorySendMore.GetInstance()},
            { Skills.WeixinTingShu,WeChatReadSendMore.GetInstance()},
        };
        private bool SendMoreResourceCmd(string skillId, string skillName, string playId, bool isUp)
        {
            Debug.WriteLine("SendMoreResourceCmd start");
            if (sdk != null && !string.IsNullOrEmpty(playId))
            {
                IsResourcesFromGetMore = true;
                VOICE_ID_CS voice_id = new VOICE_ID_CS();
                int ret = -1;
                if (MoreResourceModels.ContainsKey(skillId))
                {
                    ret = MoreResourceModels[skillId].SendMoreResourceCmd(skillId, skillName, playId, isUp, sdk, voice_id);
                }
                else
                {
                    Debug.WriteLine($"Can't hit the MoreResourceModels SkillId is {skillId}");
                    ret = MoreResourceModels[Skills.FmStory].SendMoreResourceCmd(skillId, skillName, playId, isUp, sdk, voice_id);
                }
                if (ret == 0)
                {
                    Debug.WriteLine($"voiceId:{voice_id.Get()}");

                    Session.AddSession(new Session(SessionType.MoreResource, voice_id.Get()));
                }
                else
                {
                    Debug.WriteLine($"SendMoreResourceCmd, err:{ret}!");
                }
                Debug.WriteLine($"SendMoreResourceCmd end {ret}");
                return ret == 0 ? true : false;
            }
            else
            {
                Debug.WriteLine($"SendMoreResourceCmd end false");
                return false;
            }
        }
        //public async void UninitAsync()
        //{
        //    await Task.Run(() =>
        //    {
        //        //sdk.UnInit();
        //        Log.WriteLine("UninitXiaoweiSdk");
        //    });

        //    UpdateOnlineStatus(false);
        //}

        //public Task<int> ReconnectAsync()
        //{
        //    return Task.Run(() =>
        //    {
        //        //return sdk.tx_device_reconnect_cs();
        //        return 0;
        //    });
        //}

        private void OnLoginComplete(int result)
        {
            Debug.WriteLine($"OnLoginComplete, result:{result}");
            Task.Run(() => {
                if (result == 0)
                {
                    if (!IsInitialized)
                    {
                        SdkLoginSuccess();
                        IsInitialized = true;
                    }
                }
            });

        }

        public void UpdateAppList(IList<string> appList)
        {
            sdk._xiaowei_set_words_list((TXCA_WORDS_TYPE_CS)100, appList);
        }

        public bool HasBinders()
        {
            IList<TX_BINDER_INFO_CS> binders = new List<TX_BINDER_INFO_CS>();
            int ret;
            do
            {
                Task.Delay(300).Wait();
                binders.Clear();
                ret = sdk._device_get_binder_list(binders);
                Debug.WriteLine($"ret = {ret}, binders.Count:{binders.Count}");
            } while (ret != 0);

            GetBinderInfo(binders);

            return binders.Count > 0;
        }

        private void SdkLoginSuccess()
        {
            if (sdk == null) return;

            Din = sdk._device_get_uin();

            Debug.WriteLine($"LoginSuccess, qrcode:{QRCode}, Din:{Din}");

            var hasBinder = HasBinders();
            IsLogin = hasBinder;
            if (IsLogin)
            {
                Login?.Invoke(UserId, UserName);
            }
            Initialized?.Invoke();
        }

        private void GetBinderInfo(IList<TX_BINDER_INFO_CS> binders)
        {
            if (binders.Count > 0)
            {
                UserId = binders[0].Getuser_name();
                UserName = binders[0].Getnick_name();
            }
            else
            {
                UserId = UserName = "";
            }
        }

        private void UpdateOnlineStatus(bool isOnline)
        {
            if (IsOnline != isOnline)
            {
                IsOnline = isOnline;
                if (IsInitialized)
                {
                    Debug.WriteLine($"XiaoWei SDK Online Status, {(isOnline ? "Online" : "Offline")}");
                    AvailabilityChanged?.Invoke(isOnline);
                }
            }
        }
        #region Public APIs
        public void EraseBinders()
        {
            //sdk?.tx_erase_all_binders_cs();
            sdk?._device_erase_all_binders();
        }

        public async Task AbortRequestAsync()
        {
            HasContext = false;
            if (responseEvent == Events.Idle || responseEvent == Events.IotInfo)
            {
                return;
            }

            await Task.Run(() =>
            {
                Debug.WriteLine($"_xiaowei_request_cancel");
                _ = sdk?._xiaowei_request_cancel("");
            }).ConfigureAwait(false);
        }

        public void RequestQrCodeUpdate()
        {
            sdk?._device_refresh_qr_code();
        }

        private class ResponseParamCanAwait
        {
            public ManualResetEvent waiter { get; } = new ManualResetEvent(false);
            public ResponseParam ResponseParam { get; set; }
        }
        private readonly Dictionary<string, ResponseParamCanAwait> defaultVoiceIdTable = new Dictionary<string, ResponseParamCanAwait>();

        public string TextToVoice(string text)
        {
            if (sdk != null && IsOnline)
            {
                Debug.WriteLine($"RequestTTSAsync, {text}");
                VOICE_ID_CS voice_id = new VOICE_ID_CS();
                var bytes = Encoding.UTF8.GetBytes(text);
                lock (defaultVoiceIdTable)
                {
                    var ret = sdk._xiaowei_request(voice_id, TXCA_CHAT_TYPE_CS.txca_chat_only_tts, bytes, (uint)bytes.Length, new TXCA_PARAM_CONTEXT_CS());
                    defaultVoiceIdTable[voice_id.Get()] = new ResponseParamCanAwait();
                }
                if (defaultVoiceIdTable[voice_id.Get()].waiter.WaitOne(4000)){
                    ResponseParam response = defaultVoiceIdTable[voice_id.Get()].ResponseParam;
                    defaultVoiceIdTable[voice_id.Get()].waiter.Dispose();
                    defaultVoiceIdTable.Remove(voice_id.Get());
                    return ResponseHelper.GetTTSUrl(response);
                }
                return "";
            }
            else
            {
                Debug.WriteLine("You should login first!");
                return null;
            }
        }

        #endregion

        #region Alarm related functions
        public bool FetchAlarmList()
        {
            if (sdk != null && IsOnline)
            {
                VOICE_ID_CS voice_id = new VOICE_ID_CS();
                var ret = sdk._xiaowei_request_cmd(voice_id, cmd: "ALARM", sub_cmd: "get_list", param: "");
                if (ret == 0)
                {
                    Debug.WriteLine($"FetchAlarmList voiceId:{voice_id.Get()}");
                    Session.AddSession(new Session(SessionType.FetchAlarmList, voice_id.Get()));
                }
                else
                {
                    Debug.WriteLine($"FetchAlarmList,err:{ret}!");
                }
                return ret == 0 ? true : false;
            }
            else
            {
                return false;
            }
        }

        public async Task ModifyAlarmAsync(int opt, JsonObject clockInfo)
        {
            await Task.Run(() =>
            {
                if (IsOnline)
                {
                    JsonObject json = new JsonObject();
                    json.SetNamedValue("opt", JsonValue.CreateNumberValue(opt));
                    json.SetNamedValue("clock_info", clockInfo);
                    var voice_id = new VOICE_ID_CS();
                    //sdk.txca_set_alarm_info_cs("", optType, alarm_info_json);
                    var ret = sdk._xiaowei_request_cmd(voice_id, cmd: "ALARM", sub_cmd: "set_alarm", param: json.ToString());
                    if (ret == 0)
                    {
                        Debug.WriteLine($"ModifyAlarmAsync, voice_id:{voice_id}");

                        Session.AddSession(new Session(SessionType.ModifyAlarm, voice_id.Get()));
                    }
                    else
                    {
                        Debug.WriteLine($"ModifyAlarmAsync, err:{ret}!");
                    }
                }
            }).ConfigureAwait(false);

        }
        #endregion

        #region IOT function
        public async Task<bool> GetDeviceListAsync()
        {
            return await Task<bool>.Run(() =>
            {
                if (IsOnline)
                {
                    Debug.WriteLine("GetDeviceListAsync");
                    //sdk.txca_get_iot_device_list_cs("");
                    sdk?._xiaowei_get_iot_device_list();
                    return true;
                }
                else
                {
                    return false;
                }
            }).ConfigureAwait(false);
        }

        public void BeatHeart()
        {
            sdk?._xiaowei_get_iot_device_list();
        }

        public Task CheckNetworkState()
        {
            return Task.Run(() =>
            {
                Debug.WriteLine("Check network state");
                //sdk.txca_get_iot_device_list_cs("");
                sdk?._xiaowei_get_iot_device_list();

            });
        }
        #endregion

        #region Send keyword Audio related functions
        public void StartKeywordRecognize()
        {
            isKeywordRecognizing = true;
            isFirstFrameOfKeyword = true;
            DebugWhenFrameReady("StartKeywordRecognizing");
        }

        private void DebugWhenFrameReady(string tag)
        {
            Debug.WriteLine($"{tag}, isFirstFrameOfKeyword:{isFirstFrameOfKeyword}, " +
                $"isKeywordRecognizing:{isKeywordRecognizing}, " +
                $"isFirstFrameOfSpeech:{isFirstFrameOfSpeech}, " +
                $"isSpeechRecognizing:{isSpeechRecognizing}, " +
                $"isSpeechRecognizingFinished:{isSpeechRecognizingFinished}");
        }

        private bool isFirstFrameOfKeyword = true;
        public void SendKeywordAudio(byte[] pcm)
        {
            if (pcm == null) return;
            if (!isKeywordRecognizing) return;

            if (isFirstFrameOfKeyword)
            {
                Debug.WriteLine("!!!KWS START.");
                isFirstFrameOfKeyword = false;
                Session.DeleteSessionByType(SessionType.KeywordSpotting);
                SendFirstAudioFrame(pcm, isKeyword: true);
                Debug.WriteLine("KWS START 2.");
            }
            else
            {
                SendAudioFrame(pcm, isKeyword: true);
            }
        }
        #endregion

        #region Send Speech Audio related functions
        public void StartSpeechRecognize()
        {
            isSpeechRecognizing = true;
            isFirstFrameOfSpeech = true;
            isSpeechRecognizingFinished = false;
            DebugWhenFrameReady("StartSpeechRecognize");
        }

        public void StopSpeechRecognize()
        {
            isSpeechRecognizing = false;
            DebugWhenFrameReady("StopSpeechRecognize");
        }

        private bool isFirstFrameOfSpeech = true;
        public async Task SendSpeechAudio(byte[] pcm)
        {
            if (pcm == null)
            {
                Debug.WriteLine($"SendSpeechAudio fail: pcm is null");
                return;
            }

            if (!isSpeechRecognizing)
            {
                Debug.WriteLine($"SendSpeechAudio fail: not SpeechRecognizing");
                return;
            }

            if (isFirstFrameOfSpeech)
            {
                isFirstFrameOfSpeech = false;
                Debug.WriteLine("!!!SPEECH START.");
                responseArrived = false;
                Session.DeleteSessionByType(SessionType.SpeechRequest);
                SendFirstAudioFrame(pcm, isKeyword: false);
                Debug.WriteLine("SPEECH START 2.");
            }
            else
            {
                Debug.WriteLine("SendAudioFrame");
                SendAudioFrame(pcm, isKeyword: false);
                Debug.WriteLine("SendAudioFrame end");
            }
        }

        #endregion

        #region Send Audio Frame
        public void SendFirstAudioFrame(byte[] pcm, bool isKeyword)
        {
            if (pcm == null) return;

            TXCA_PARAM_CONTEXT_CS ctx = new TXCA_PARAM_CONTEXT_CS();
            if (!string.IsNullOrEmpty(Context.Id))
            {
                ctx.Setid(Context.Id);
                ctx.Setsilent_timeout(Context.SilentTimeout);
                ctx.Setspeak_timeout(Context.SpeakTimeout);
            }
            else
            {
                ctx.Setsilent_timeout(500);
                ctx.Setspeak_timeout(5000);
            }
            ctx.Setvoice_request_begin(true);
            ctx.Setvoice_request_end(false);

            SendRequest(pcm, ctx, isKeyword);
        }

        public void SendAudioFrame(byte[] pcm, bool isKeyword)
        {
            if (pcm == null) return;

            TXCA_PARAM_CONTEXT_CS ctx = new TXCA_PARAM_CONTEXT_CS();
            if (!string.IsNullOrEmpty(Context.Id))
            {
                ctx.Setid(Context.Id);
                ctx.Setsilent_timeout(Context.SilentTimeout);
                ctx.Setspeak_timeout(Context.SpeakTimeout);
            }
            else
            {
                ctx.Setsilent_timeout(500);
                ctx.Setspeak_timeout(5000);
            }
            ctx.Setvoice_request_begin(false);
            ctx.Setvoice_request_end(false);

            SendRequest(pcm, ctx, isKeyword);
        }

        public void SendLastAudioFrame(byte[] pcm, bool isKeyword)
        {
            if (pcm == null) return;

            TXCA_PARAM_CONTEXT_CS ctx = new TXCA_PARAM_CONTEXT_CS();
            if (!string.IsNullOrEmpty(Context.Id))
            {
                ctx.Setid(Context.Id);
                ctx.Setsilent_timeout(Context.SilentTimeout);
                ctx.Setspeak_timeout(Context.SpeakTimeout);
            }
            else
            {
                ctx.Setsilent_timeout(500);
                ctx.Setspeak_timeout(5000);
            }
            ctx.Setvoice_request_begin(false);
            ctx.Setvoice_request_end(true);

            SendRequest(pcm, ctx, isKeyword);
        }

        #endregion

        #region SendRequest
        private void SendRequest(byte[] pcm, TXCA_PARAM_CONTEXT_CS context, bool isKeyword)
        {
            if (pcm == null || context == null || !IsOnline)
            {
                Debug.WriteLine($"SendRequest fail, pcm:{pcm == null}  isOnline:{IsOnline} ");
                return;
            }

            SessionType type;
            if (isKeyword && KeywordVerificationEnabled)
            {
                context.Setwakeup_type(TXCA_WAKEUP_TYPE_CS.txca_wakeup_type_local_with_text);
                context.Setwakeup_word("小微小微");
                context.Setsilent_timeout(100);
                context.Setspeak_timeout(5000);
                type = SessionType.KeywordSpotting;
            }
            else
            {
                type = SessionType.SpeechRequest;
            }

            Debug.WriteLine("_xiaowei_request");
            VOICE_ID_CS voice_id = new VOICE_ID_CS();
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            var ret = sdk._xiaowei_request(voice_id, TXCA_CHAT_TYPE_CS.txca_chat_via_voice, pcm, (uint)pcm.Length, context);
            sw.Stop();
            if (ret == 0)
            {
                Debug.WriteLine($"_xiaowei_request, ok, len:{pcm.Length} voice_id:{voice_id.Get()}");
                Session.AddSession(new Session(type, voice_id.Get()));
            }
            else
            {
                Debug.WriteLine($"_xiaowei_request, err:{ret}, type:{type}, elapsed:{sw.ElapsedMilliseconds}ms.");
            }
        }

        #endregion

        #region Process response
        private void ProcessSpeechResponseInWorkThread(ResponseData data)
        {
            if (data == null) return;
            Debug.WriteLine($"ProcessSpeechResponseInWorkThread, voiceId:{data.VoiceId}, Evt:{data.Evt}, " +
                $"skill name:{data.Param.Skill.Name}, skill id:{data.Param.Skill.Id}");
            switch (data.Evt)
            {
                case Events.Idle:
                    if (IsSpeechRequest(data.VoiceId))
                    {
                        if (responseArrived)
                        {
                            EnsureSpeechRecognizeDone("");
                            ProcessEnded?.Invoke();
                        }
                    }

                    break;
                case Events.Recognize:
                    ProcessSpeechRecognizeInWorkerThread(data.ExtendInfo);
                    break;
                case Events.IotInfo:
                    break;
                case Events.Response:
                    if (IsSpeechRequest(data.VoiceId))
                    {
                        EnsureSpeechRecognizeDone("");
                    }
                    responseArrived = true;
                    OnResponseEvent(data.Param);
                    break;
                default: break;
            }
            Debug.WriteLine($"ProcessSpeechResponseInWorkThread end, voiceId:{data.VoiceId}, Evt:{data.Evt}, " +
                $"skill name:{data.Param.Skill.Name}, skill id:{data.Param.Skill.Id}");
        }

        private bool IsSpeechRequest(string voiceId)
        {
            var type = Session.GetTypeByVoiceId(voiceId);
            return (type != null && type == SessionType.SpeechRequest);
        }
        //private bool IsKeywordSpotting(string voiceId)
        //{
        //    var type = Session.GetTypeByVoiceId(voiceId);
        //    return (type != null && type == SessionType.KeywordSpotting);
        //}

        private void OnResponseEvent(ResponseParam param)
        {
            var err = param.ErrorCode;
            HasError = err != 0;

            if (param.Context != null)
            {
                Context = param.Context;
                HasContext = !string.IsNullOrEmpty(Context.Id);
            }
            else
            {
                HasContext = false;
            }

            if (HasError && string.IsNullOrEmpty(param.ResponseText))
            {
                Debug.WriteLine($"OnResponseEvent err:{err}");
                ResponseError?.Invoke(err);
                return;
            }

            PlayId = ResponseHelper.GetPlayId(param);

            HasPlayable = ResponseHelper.HasPlayableResource(param);
            IResponseData responseData= ResponseCreator.MakeResponseData(this,param);
            ResponseReceived?.Invoke(responseData);
        }

        private bool IsRecognizeFinished(string jsonString, out string text)
        {
            try
            {
                var jsonObject = JsonObject.Parse(jsonString);
                var evt = jsonObject.GetNamedString("event", "");
                //var voiceId = jsonObject.GetNamedString("voiceid", "");
                text = jsonObject.GetNamedString("text", "");
                return evt == "RecognizeEnd";
            }
            catch (Exception)
            {
                text = "";
                return false;
            }
        }

        private void ProcessSpeechRecognizeInWorkerThread(string jsonString)
        {
            if (!IsRecognizeFinished(jsonString, out string text))
            {
                isSpeechRecognizingFinished = false;
                SpeechRecognizing?.Invoke(text);
            }
            else
            {
                EnsureSpeechRecognizeDone(text);
            }
        }

        private void EnsureSpeechRecognizeDone(string text)
        {
            if (!isSpeechRecognizingFinished)
            {
                isSpeechRecognizingFinished = true;

                Debug.WriteLine($"!!!SPEECH DONE. {text}");
                //SpeechRecognized is not ready for all times. Sometimes it is lost.
                SpeechRecognized?.Invoke(text);
            }
        }

        #endregion

        #region ReportState

        public void ReportState()
        {
            ReportState(Skill, playId: "135", content: "This is a idle state", PlayMode.SequentialNoLoop, PlayState.Idle, 0);
        }

        public void ReportState(Skill skill, string playId, string content, PlayMode mode, PlayState state, ulong offset)
        {
            DeviceState device_state;
            device_state.Skill = skill;
            device_state.PlayState = state;
            device_state.Availability = DeviceAvailability.Default;
            device_state.PlayId = playId;
            device_state.PlayContent = content;

            device_state.PlayOffset = offset;
            device_state.PlayMode = mode;
            device_state.ResourceType = ResourceFormat.Text;
            device_state.ResouceName = "";
            device_state.Performer = "";

            device_state.Collection = "";
            device_state.CoverUrl = "";
            device_state.Volume = 100;
            device_state.Brightness = 100;

            _ = ReportStateAsync(device_state);
        }

        public int? ReportStateSync(DeviceState state)
        {
            int? result = sdk?._xiaowei_report_state(TypeHelper.DeviceStateConvert(state));
            return result;
        }

        public Task<int?> ReportStateAsync(DeviceState state)
        {
            return Task.Run(() =>
            {
                return ReportStateSync(state);
            });
        }
        #endregion

        public bool GetResourceURL(string[] playIds)
        {


            if (sdk != null)
            {
                IsResourcesFromGetMore = true;
                JsonObject joOject = new JsonObject();
                JsonArray array = new JsonArray();
                foreach (var item in playIds)
                {
                    array.Add(JsonValue.CreateStringValue(item));
                }
                joOject.SetNamedValue("res_id_list", array);
                Debug.WriteLine($"GetResourceURL, json:{joOject}");
                VOICE_ID_CS voice_id = new VOICE_ID_CS();
                var ret = sdk._xiaowei_request_cmd(voice_id, cmd: "8dab4796-fa37-4114-0051-7637fa2b0001", sub_cmd: "1010203", param: joOject.ToString());
                if (ret == 0)
                {
                    Debug.WriteLine($"voiceId:{voice_id.Get()}");

                    Session.AddSession(new Session(SessionType.MoreResource, voice_id.Get()));
                }
                else
                {
                    Debug.WriteLine($"GetResourceURL, err:{ret}!");
                }
                Debug.WriteLine($"GetResourceURL end {ret}");
                return ret == 0 ? true : false;
            }
            else
            {
                Debug.WriteLine($"GetResourceURL end false");
                return false;
            }
        }
    }
}
